/*
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Library General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor Boston, MA 02110-1301,  USA
 */

/**
 * @version runner.c 0.0.1
 * @package Comoro
 * @author Dr.NP <np@bsgroup.org>
 * 
 * Center server runner operator
 */

#include "bspd.h"

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

#include <stdlib.h>
#include <string.h>

#ifdef HAVE_MALLOC_H
#ifndef __OpenBSD__
#include <malloc.h>
#endif
#endif

extern struct conf setting;

// Runner pool
struct runner *runner_list[MAX_RUNNER];

/* Initializer */
int runner_init()
{
	int i;
	int ret;
	char runner_name[256];
	char script_filename[256];
	char query[1024];
	struct mysql_row **row;
	int num_columns;
	
	for (i = 0; i < MAX_RUNNER; i ++)
	{
		runner_list[i] = (struct runner *) NULL;
	}
	
	// We need first runner
	if (0 == db_mysql_create_env(0))
	{
		sprintf(query, "SELECT runner_id, runner_name, runner_script, runner_need_login FROM %s ORDER BY runner_id", setting.mysql_runner_table);
		if (0 != db_mysql_query(0, query))
		{
			fprintf(stderr, "Runner list load error\n");
			log_add("Runner list load error : MySQL query error");
			exit(EXIT_DB_ERROR);
		}
		
		while (1)
		{
			row = db_mysql_fetch_row(0, &num_columns);
			if (!row || 0 == num_columns)
			{
				// Last row passed
				break;
			}
			
			i = (row[0]->value && strlen(row[0]->value)) ? atoi(row[0]->value) : -2;
			strncpy(runner_name, row[1]->value, 255);
			strncpy(script_filename, row[2]->value, 255);
			
			// Load scripts
			if (i == runner_new(i))
			{
				ret = runner_load_script(runner_list[i], runner_name, script_filename);
				if (row[3]->value && strlen(row[3]->value))
				{
					runner_list[i]->need_login = (char) atoi(row[3]->value);
				}
			}
			
			db_mysql_del_row(row, num_columns);
		}
	}
	
	else
	{
		fprintf(stderr, "Runner list load error\n");
		log_add("Runner list load error : MySQL env creation error");
		exit(EXIT_DB_ERROR);
	}
	
	return RTN_SUCCESS;
}

/* Make a new runner */
int runner_new(int runner_id)
{
	if (runner_id < 1 || runner_id >= MAX_RUNNER)
	{
		return RTN_GENERAL;
	}
	
	struct runner *r = (struct runner *) malloc(sizeof(struct runner));
	int ret = runner_add(r, runner_id);
	
	if (runner_id != ret)
	{
		free(r);
		return RTN_GENERAL;
	}
	
	r->runner_id = runner_id;
	r->mysql_env_id = -1;
	r->runner_name = (char *) NULL;
	r->script_filename = (char *) NULL;
	r->state = luaL_newstate();
	luaL_openlibs(r->state);
	r->need_login = 0x1;
	r->is_loaded = 0x0;
	r->load_time = 0;
	
	return ret;
}

/* Reload a runner */
int runner_reload(int runner_id)
{
	if (runner_id < 1)
	{
		return RTN_GENERAL;
	}
	
	struct runner *r = runner_get(runner_id);
	
	if (r)
	{
		char *runner_name = strdup(r->runner_name);
		char *script_filename = strdup(r->script_filename);
		runner_unload_script(r);
		
		// Open a new state
		r->state = luaL_newstate();
		luaL_openlibs(r->state);
		runner_load_script(r, runner_name, script_filename);
		free(script_filename);
		free(runner_name);
	}
	
	return runner_id;
}

/* Load LUA script to state */
int runner_load_script(struct runner *r, char *runner_name, char *script_filename)
{
	const char *res;
	
	if (!r || !script_filename || !r->state)
	{
		return RTN_GENERAL;
	}
	
	// Load lua script
	if (luaL_loadfile(r->state, script_filename) || lua_pcall(r->state, 0, 0, 0))
	{
		res = lua_tostring(r->state, -1);
		log_add("Runner %d load LUA script error: %s", r->runner_id, res);
		// lua_close(r->state);
		
		return RTN_UNKNOWN;
	}
	
	// Successfully
	r->is_loaded = 0x1;
	luaL_openlibs(r->state);
	
	// Regist C functions
	func_regist(r->state);
	
	// OK
	if (r->script_filename)
	{
		free(r->script_filename);
	}

	if (r->runner_name)
	{
		free(r->runner_name);
	}
	
	r->script_filename = strdup(script_filename);
	r->runner_name = strdup(runner_name);
	
	if (r->runner_id > 0)
	{
		
		// Init MySQL
		if (db_mysql_create_env(r->runner_id) == r->runner_id)
		{
			r->mysql_env_id = r->runner_id;
		}
		
		// Init runner
		lua_pushinteger(r->state, r->runner_id);
		lua_setglobal(r->state, "runner_id");
		
		lua_pushinteger(r->state, r->mysql_env_id);
		lua_setglobal(r->state, "mysql_env_id");

		if (0 == r->load_time)
		{
			// Try initialize function
			lua_getglobal(r->state, FUN_INIT);
			if (lua_pcall(r->state, 0, 0, 0))
			{
				log_add("Runner %d has no available initialize function <%s>", r->runner_id, FUN_INIT);
			}
		}

		// Try on-load function
		lua_getglobal(r->state, FUN_LOAD);
		if (lua_pcall(r->state, 0, 0, 0))
		{
			log_add("Runner %d has no available initialize function <%s>", r->runner_id, FUN_LOAD);
		}

		r->load_time ++;
		runner_clear_stack(r->state);
	}

	fprintf(stderr, "Runner: [%s] script: [%s] loaded at table item %d\n", runner_name, script_filename, r->runner_id);
	log_add("Runner %d load LUA script [%s]", r->runner_id, script_filename);
	
	return r->runner_id;
}

/* Unload state */
int runner_unload_script(struct runner *r)
{
	if (r && r->state)
	{
		lua_close(r->state);
		r->is_loaded = 0x0;
		
		if (r->mysql_env_id >= 0)
		{
			db_mysql_close_env(r->mysql_env_id);
			r->mysql_env_id = -1;
		}
		
		log_add("Runner %d unloaded", r->runner_id);
	}
	
	return r->runner_id;
}

/* Add a runner into list */
int runner_add(struct runner *r, int runner_id)
{
	if (runner_list[runner_id] == (struct runner *) NULL)
	{
		runner_list[runner_id] = r;
		
		return runner_id;
	}
	
	return RTN_GENERAL;
}

/* Delete a runner from list */
int runner_del(struct runner *r)
{
	runner_unload_script(r);
	r->mysql_env_id = -1;
	
	if (r->script_filename)
	{
		free(r->script_filename);
		r->script_filename = (char *) NULL;
	}
	
	return RTN_SUCCESS;
}

/* Get the runner object by given id */
struct runner * runner_get(int runner_id)
{
	struct runner *r = (runner_list[runner_id]) ? runner_list[runner_id] : (struct runner *) NULL;
	
	return r;
}

/* Get MySQL env by given state */
int runner_get_mysql_env(lua_State *l)
{
	int i;
	
	for (i = 0; i < MAX_RUNNER; i ++)
	{
		if (runner_list[i] && runner_list[i]->state == l)
		{
			return i;
		}
	}
	
	return RTN_GENERAL;
}

/* Input data array, run script */
void runner_go(int runner_id, int client_id, unsigned int cmd_id, char **params, unsigned int plen)
{
	unsigned int i;
	int ret = 0;
	const char *res;
	
	struct runner *r = runner_get(runner_id);
	if (!r)
	{
		fprintf(stderr, "Runner error %d\n", runner_id);
		return;
	}
	
	runner_clear_stack(r->state);
	
	// Method <proc_cmd> must be defined
	lua_getglobal(r->state, FUN_GO);
	lua_pushinteger(r->state, client_id);
	lua_pushinteger(r->state, cmd_id);
	
	// Push hash table
	lua_newtable(r->state);
	
	for (i = 0; i < plen; i ++)
	{
		lua_pushinteger(r->state, i + 1);
		lua_pushstring(r->state, params[i]);
		lua_settable(r->state, -3);
	}
	
	ret = lua_pcall(r->state, 3, 0, 0);
	
	if (ret)
	{
		res = lua_tostring(r->state, -1);
		log_add("Lua function <%s> error:[%d] - %s", FUN_GO, ret, res);
	}
	
	// Clear stack
	runner_clear_stack(r->state);
	
	return;
}

/* Client offline */
void runner_offline(int runner_id, int client_id)
{
	int ret = 0;
	const char *res;
	struct runner *r = runner_get(runner_id);
	
	if (!r)
	{
		return;
	}
	
	runner_clear_stack(r->state);
	
	// Method <proc_offline> must be defined
	lua_getglobal(r->state, FUN_OFFLINE);
	lua_pushinteger(r->state, client_id);
	
	ret = lua_pcall(r->state, 1, 0, 0);
	
	if (ret)
	{
		res = lua_tostring(r->state, -1);
		log_add("Lua function <%s>  error:[%d] - %s", FUN_OFFLINE, ret, res);
	}
	
	// Clear stack
	runner_clear_stack(r->state);
	
	return;
}

/* Call runner function */
void runner_call(lua_State *state, char *func, int client_id, char **params, unsigned int plen)
{
	unsigned int i;
	int ret = 0;
	const char *res;
	
	if (!state)
	{
		return;
	}
	
	runner_clear_stack(state);
	
	// Lua function [func] must be defined as foo(int, char **) model
	lua_getglobal(state, func);
	lua_pushinteger(state, client_id);
	
	// Push hash table
	lua_newtable(state);
	
	for (i = 0; i < plen; i ++)
	{
		lua_pushinteger(state, i + 1);
		lua_pushstring(state, params[i]);
		lua_settable(state, -3);
	}
	
	ret = lua_pcall(state, 2, 0, 0);
	
	if (ret)
	{
		res = lua_tostring(state, -1);
		log_add("Lua function <%s> error:[%d] - %s", func, ret, res);
	}
	
	// Clear stack
	runner_clear_stack(state);
}

/* Clear state stack */
void runner_clear_stack(lua_State *l)
{
	if (l)
	{
		lua_settop(l, 0);
	}
	
	return;
}

/* LUA stack dump */
void runner_dump_stack(lua_State *l)
{
	int i;
	int top = lua_gettop(l);
	
	if (top < 1)
	{
		fprintf(stderr, "STACK IS EMPTY!!!\n");
		return;
	}
	
	fprintf(stderr, "%d ITEMS IN STACK =========================================================\n", top);
	
	for (i = 1; i <= top; i ++)
	{
		int t = lua_type(l, i);
		switch (t)
		{
			case LUA_TSTRING :
				fprintf(stderr, "\tString : '%s'\n", lua_tostring(l, i));
				break;
			
			case LUA_TBOOLEAN :
				fprintf(stderr, lua_toboolean(l, i) ? "\tBoolean : true\n" : "\tBoolean : false\n");
				break;
			
			case LUA_TNUMBER :
				fprintf(stderr, "\tNumber : %g\n", lua_tonumber(l, i));
				break;
			
			case LUA_TNIL :
				fprintf(stderr, "\tNil : nil\n");
				break;
			
			case LUA_TTABLE :
				fprintf(stderr, "\tTable : table\n");
				break;
			
			case LUA_TTHREAD :
				fprintf(stderr, "\tThread : thread\n");
				break;
			
			case LUA_TUSERDATA :
				fprintf(stderr, "\tUserData : userdata\n");
				break;
			
			case LUA_TFUNCTION :
				fprintf(stderr, "\tFunction : function\n");
				break;
			
			default :
				fprintf(stderr, "\t*Unknown : %s\n", lua_typename(l, t));
				break;
		}
	}
}
